package org.opens.guava.utilites.eventBus.myeventbus;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;

public class MyDispatcher {

    private final Executor executor;

    private final MyEventExceptionHandle exceptionHandle;

    public static final Executor SEQ_EXECUTOR_SERVICE = SeqExecutorService.INSTANCE;
    public static final Executor PRE_THREAD_EXECUTOR_SERVICE = PreThreadExecutorService.INSTANCE;

    public MyDispatcher(Executor executor, MyEventExceptionHandle exceptionHandle) {
        this.executor = executor;
        this.exceptionHandle = exceptionHandle;
    }

    public void dispatch(MyBus bus, MyRegistryTable table, Object event, String topic) {
        ConcurrentLinkedQueue<MySubscriber> mySubscribers = table.scanSubscriber(topic);
        if(null == mySubscribers) {
            if(exceptionHandle != null) {
                //TODO 表示未完成该方法: 完成handle的第二个参数的写入
                exceptionHandle.handle(new IllegalArgumentException("the topic " + topic + " not bind yet."), new BaseMyEventContext(bus.getBusName(), null, event));
            }
            return;
        }

        mySubscribers.stream().filter(s -> !s.isDisable()).filter(s -> {
            Method method = s.getMySubscribeMethod();
            Class<?> aClass = method.getParameterTypes()[0];
            //过滤掉事件参数类型和方法参数类型不一致的方法
            return aClass.isAssignableFrom(event.getClass());
        }).forEach(s -> realInvoke(s, event, bus));

    }

    private void realInvoke(MySubscriber subscriber, Object event, MyBus bus) {
        Method method = subscriber.getMySubscribeMethod();
        Object object = subscriber.getMySubscribeObject();
        executor.execute(() -> {
            try {
                method.invoke(object, event);
            } catch (Exception e) {
                if(null != exceptionHandle) {
                    //TODO 上下文主题未完成
                    exceptionHandle.handle(e, new BaseMyEventContext(bus.getBusName(), subscriber, event));
                }
            }
        });
    }

    //FIXME 提醒该方法不是一个很好的实现, 但是只能在src.java目录下的该标记才会被扫描
    public void close() {
        if(executor instanceof ExecutorService) {
            ((ExecutorService) executor).shutdown();
        }
    }

    static MyDispatcher newDispatcher(MyEventExceptionHandle exceptionHandle, Executor executor) {
        return new MyDispatcher(executor, exceptionHandle);
    }

    static MyDispatcher seqDispatcher(MyEventExceptionHandle exceptionHandle) {
        return new MyDispatcher(SEQ_EXECUTOR_SERVICE, exceptionHandle);
    }

    static MyDispatcher preThreadDispatcher(MyEventExceptionHandle exceptionHandle) {
        return new MyDispatcher(PRE_THREAD_EXECUTOR_SERVICE, exceptionHandle);
    }

    private static class SeqExecutorService implements Executor {

        private final static SeqExecutorService INSTANCE = new SeqExecutorService();

        @Override
        public void execute(Runnable command) {
            command.run();
        }
    }

    private static class PreThreadExecutorService implements Executor {

        private final static SeqExecutorService INSTANCE = new SeqExecutorService();

        @Override
        public void execute(Runnable command) {
            new Thread(command).start();
        }
    }

    private static class BaseMyEventContext implements MyEventContext {

        private final String eventBusName;

        private final MySubscriber mySubscriber;

        private final Object event;

        public BaseMyEventContext(String eventBusName, MySubscriber mySubscriber, Object event) {
            this.eventBusName = eventBusName;
            this.mySubscriber = mySubscriber;
            this.event = event;
        }

        @Override
        public String getSource() {
            return eventBusName;
        }

        @Override
        public Object getSubscriber() {
            return mySubscriber != null ? mySubscriber.getMySubscribeObject() : null;
        }

        @Override
        public Method getSubscribe() {
            return mySubscriber != null ? mySubscriber.getMySubscribeMethod() : null;
        }

        @Override
        public Object getEvent() {
            return event;
        }
    }

}
